
package kuti.grp5;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import static java.lang.Math.PI;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import kuti.ILawn;
import kuti.ISimulParams;

/**
 * This class manages the cutting that a KUTI does.
 * 
 * The kuti of the area can be seperaded on 3 steps. First, cuttung the
 * circle how the kuti is bevor moving. Next, cutting a rectangle (between
 * start and end position). End, Cutting the circle at the end position.
 * 
 * If the kuti does not moving, we need do just the first step.
 * 
 * @author Marc Schorderet <marc.schorderet@edu.hefr.ch>
 * @author Vincenzo Do <vincenzo.do@edu.hefr.ch>
 *
 */

public class CutterManager {
    
    /**
     * Radius of the kuti
     */
    
    private final double radius;
    
    /**
     * Current kuti
     */
    
    private Kuti kuti;
    
    /**
     * LawnArea to update
     */
    
    private LawnArea lawnArea;
    
    /**
     * Total lawn of the world
     */
    
    private ILawn lawn;
    
    /**
     * Traveled distance of the kuti
     */
    
    private double distance;
    
    /**
     * Start position for this step
     */
    
    private Point2D curCenter;
    
    /**
     * All points to cut for this step
     */
    
    private Set<Point2D> toCut = new HashSet();
    
    /**
     * A part of points to cut on next step, if the kuti don't
     * move
     */
    
    private Set<Point2D> oldCut = new HashSet();
    
    /**
     * Last position of the kuti, when hi cut
     */
    
    private Point2D oldPosition = new Point2D.Double();
    
    /**
     * Create a cutter manager for the kuti.
     * 
     * @param kuti          Current kuti associed with the cutter manager
     * @param simulParams   Parameters of the simulation
     * 
     */
    
    public CutterManager(Kuti kuti, ISimulParams simulParams) {
        
        if(kuti == null){
            throw new IllegalArgumentException("The kuti is not defined.");
        }
        
        if(simulParams == null){
            throw new IllegalArgumentException("The simulation parameters are not defined.");
        }
        
        this.kuti = kuti;
        this.radius = simulParams.getKDiameter() / 2;
        
    }
    
    /**
     * This method cuts the lawn for this step.
     * 
     * @param distance  The traveled distance for this step
     * 
     * @return Returns the lawn area to update on the interface.
     * 
     */
    
    public LawnArea cutLawn(double distance){
       
        // Check if the distance is a number
        if(Double.isNaN(distance)){
            throw new IllegalArgumentException("The distance is not a numner");
        }
        
        // Check if the distance is pisitif.
        if(distance < 0.0){
            throw new IllegalArgumentException("The distance must be positif");
        }
        
        this.distance = distance;
        this.curCenter = KutiTrajectory.moveTo(kuti.getCenterPosition(), -distance, kuti.getOrientation());

        // If the kuti is on the same position, that the last step, we don't
        // need to calculate the first circle, because it's the same that the
        // end circle of the last step.
        if (oldPosition.equals(curCenter)) {

            if (distance == 0.0) {
                // Cutting the same circle
                toCut.addAll(oldCut);
            } else {

                // Cutting the same circle
                toCut.addAll(oldCut);

                // Cutting the rectangle between start and end
                toCut.addAll(getRectanglePoints(curCenter, radius, kuti.getOrientation(), distance));

                // The kuti is moving, we need to update the old cut with the end kuti
                // of this step.
                oldCut.clear();
                oldCut.addAll(getCirclePoints(kuti.getCenterPosition(), radius));

                // Update the last kuti position
                oldPosition = kuti.getCenterPosition();

                // Cutting the end circle
                toCut.addAll(oldCut);
            }

        } else {

            if (distance == 0.0) {

                // Update the old cutting area and position
                oldCut.clear();
                oldCut.addAll(getCirclePoints(curCenter, radius));
                oldPosition = kuti.getCenterPosition();

                // Cutting the start circle
                toCut.addAll(oldCut);


            } else {

                // Cutting the start circle
                toCut.addAll(getCirclePoints(curCenter, radius));

                // Cutting the rectangle between start and end
                toCut.addAll(getRectanglePoints(curCenter, radius, kuti.getOrientation(), distance));

                // Update the old cutting area and position
                oldCut.clear();
                oldCut.addAll(getCirclePoints(curCenter, radius));
                oldPosition = kuti.getCenterPosition();

                // Cutting the end circle
                toCut.addAll(getCirclePoints(kuti.getCenterPosition(), radius));
            }

        }

        // Get the lawn area to update on the interface
        lawnArea = getBounds(curCenter, kuti.getCenterPosition(), radius, lawn);

        // Cuts all points on the lawn
        Iterator it = toCut.iterator();
        while (it.hasNext()) {
            Point2D pt = (Point2D) it.next();
            if (pt.getX() >= 0 && pt.getX() < SimulParams.WORLD_WIDTH && pt.getY() >= 0 && pt.getY() < SimulParams.WORLD_HEIGHT) {
                lawnArea.cutBladeInArea((int) pt.getY(), (int) pt.getX());
            }
        }

        // Crear the area to kuti for the next step
        toCut.clear();

        return lawnArea;

    }
    
    /**
     * This method calculate the area to update on the interface.
     * 
     * @param center1   Start point of the kuti
     * @param center2   End point of the kuti
     * @param radius    Radius of the kuti
     * @param lawn      The lawn on the world
     * 
     * @return Returns the area to update on the interface.
     * 
     */
    
    private static LawnArea getBounds(Point2D center1, Point2D center2, double radius, ILawn lawn){
        
        // Check if the start ponit of the kuti is defined.
        if(center1 == null){
            throw new IllegalArgumentException("The start center of the kuti is not defined.");
        }
        
        // Check if the end ponit of the kuti is defined.
        if(center2 == null){
            throw new IllegalArgumentException("The end center of the kuti is not defined.");
        }
        
        // Check if the start point is valid
        if(Double.isNaN(center1.getX()) || Double.isNaN(center1.getY())){
            throw new IllegalArgumentException("The start center values are not a number");
        }
        
        // Check if the end point is valid
        if(Double.isNaN(center2.getX()) || Double.isNaN(center2.getY())){
            throw new IllegalArgumentException("The end center values are not a number");
        }
        
        // Check if the lawn is valid
        if(lawn == null){
            throw new IllegalArgumentException("The lawn is not defined.");
        }
        
        // Create a rectangle with the two points to get the bound area.
        Rectangle2D r = new Line2D.Double(center1, center2).getBounds2D();
        
        // Resize the area
        int x = (int)(r.getX()-radius);
        int y = (int)(r.getY()-radius);
        int w = (int)(r.getWidth()+2*radius) ;
        int h = (int)(r.getHeight()+2*radius);
        
        // Check values are on the area
        x = (x < 0) ? 0 : x;
        y = (y < 0) ? 0 : y;
        w = (x+w >= SimulParams.WORLD_WIDTH) ? SimulParams.WORLD_WIDTH-1-x : w;
        h = (y+h >= SimulParams.WORLD_HEIGHT) ? SimulParams.WORLD_HEIGHT-1-y : h;
        
        return (LawnArea) lawn.getLawnHeight(x, y, w, h);
        
    }
    
    /**
     * This method calculates all points in the circle.
     * 
     * @param p         Center of the circle
     * @param radius    Radius of the circle
     * 
     * @return Returns all points in the circle
     * 
     */
    
    private static Set<Point2D> getCirclePoints(Point2D p, double radius){
       
        // Check if the current center is valid
        if(p == null){
            throw new IllegalArgumentException("The curent point is not valid.");
        }
        
        // Check if the current center is a number
        if(Double.isNaN(p.getX()) || Double.isNaN(p.getY())){
            throw new IllegalArgumentException("The current point values are not a numner");
        }
        
        // Check if the radius a number
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a number");
        }
        
        // Check if the radius is positif
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif");
        }
        
        Set<Point2D> points = new HashSet();
        
        // Create the left-botom and right-top points of the area arround the circle
        Point2D leftBottom = RadarHelper.doubleToIntPoint(new Point2D.Double(p.getX()-radius, p.getY()-radius), false, false);
        Point2D rightTop =   RadarHelper.doubleToIntPoint(new Point2D.Double(p.getX()+radius, p.getY()+radius), true, true);
        
        int x1 = (int)leftBottom.getX();
        int y1 = (int)leftBottom.getY();
        
        int x2 = (int)rightTop.getX();
        int y2 = (int)rightTop.getY();
        
        // Calculate the square radius of the kuti
        double radiusSq = radius * radius;
        
        // Check all points of the square
        for(int i = x1; i < x2; i++){
            for(int j = y1; j < y2; j++){
                Point2D pt = new Point2D.Double(i, j);
                if(isInCircle(p, pt, radiusSq))
                    points.add(pt);
            }
        }
        
        return points;
        
    }
    
    /**
     * This method retunrs all points of the rectangle (between start and end point)
     * 
     * @param curCenter     The current center of the kuti
     * @param radius        The radius of the kuti
     * @param orientation   The orientation of the kuti
     * @param distance      The traveled distance on this step
     * 
     * @return Returns all point of the rectangle.
     * 
     */
    
    private static Set<Point2D> getRectanglePoints(Point2D curCenter, double radius, double orientation, double distance){
        
        // Check if the current center is valid
        if(curCenter == null){
            throw new IllegalArgumentException("The curent point is not valid.");
        }
        
        // Check if the current center is a number
        if(Double.isNaN(curCenter.getX()) || Double.isNaN(curCenter.getY())){
            throw new IllegalArgumentException("The current point values are not a numner");
        }
        
        // Check if the radius a number
        if(Double.isNaN(radius)){
            throw new IllegalArgumentException("The radius is not a number");
        }
        
        // Check if the radius is positif
        if(radius < 0.0){
            throw new IllegalArgumentException("The radius must be positif");
        }
        
        // Check if the orientation a number
        if(Double.isNaN(orientation)){
            throw new IllegalArgumentException("The orientation is not a numner");
        }
        
        // Check if the orientation is between o and 2PI
        if(orientation < 0.0 || orientation > 2*PI){
            throw new IllegalArgumentException("The orientation is not between 0.0 and " + 2 * PI + ", current value [" + orientation + "]");
        }
        
        // Check if the distance is a number
        if(Double.isNaN(distance)){
            throw new IllegalArgumentException("The distance is not a number");
        }
        
        // Check ig the distance is positif
        if(distance < 0.0){
            throw new IllegalArgumentException("The distance must be positif");
        }
        
        Set<Point2D> points = new HashSet();
        Set<Point2D> begin = new HashSet();
        
        // Create a point, right of the kuti center
        Point2D np = KutiTrajectory.moveTo(curCenter, radius, KutiTrajectory.rotationRight(orientation, PI/2));
        
        // Create a new orientation with a right rotation (PI/"
        Double orientationSec = (orientation + PI/2) % (2*PI);
        
        // Get all point of this segment
        begin.addAll(RadarHelper.getPointProfile(np, orientationSec, 2*radius));
        
        // For all points of the kuti segment, all points of the trajectory
        Iterator it = begin.iterator();
        while(it.hasNext()){
            Point2D start = (Point2D) it.next();
            points.addAll(RadarHelper.getPointProfile(start, orientation, distance));   
        }
        
        return points;
        
    }
    
    /**
     * Check if a point is in the circle
     * 
     * @param center    Center of the kuti
     * @param point     Point to check
     * @param radiusSq  Square of the radius
     * 
     * @return Returns true if the point is in the circle, else returns false.
     * 
     */
    
    private static boolean isInCircle(Point2D center, Point2D point, double radiusSq){
        return center.distanceSq(point) <= radiusSq;
    }
    
    
    /**
     * Set the lawn
     * 
     * @param lawn  Current lawn of the world
     * 
     */
    
    public void setLawn(ILawn lawn){
        this.lawn = lawn;
    }

}
